//============================================================================
// Name        : Ensemble_Classifiers.h
// Author      : Andrey Bicalho Santos e Clayson Sandro Celes
// Version     :
// Copyright   : if you are making money with it... please give me some, because I have no money!
// Description : Genetic Algorithm in C++, Ansi-style
//============================================================================

#include <cstdlib>
#include <iostream>
#include <fstream>
#include <stdio.h>
#include <cstring>
#include <vector>
#include "Individual.h"

using namespace std;

typedef struct{
	int x,y;
}Dimension;

typedef struct{
	int weight1,weight2,weight3;
	double oa;
}GAResult;

void pera(){
	string x; cin >> x;
}


void split(const std::string & str, const string & delimiters, std::vector<std::string> * elems);
void printData(double **data, int x, int y);
int count_lines(const char* fileName);
double **load_full_file(const char* fileName,Dimension *dim_data);
double **getDataConfig1Training(double **data,Dimension *data_training, Dimension *dim_data_full);
double **getDataConfig2Training(double **data,Dimension *data_training, Dimension *dim_data_full);
double **getDataConfig3Training(double **data,Dimension *data_training, Dimension *dim_data_full);
double **getDataConfig4Training(double **data,Dimension *data_training, Dimension *dim_data_full);
double **getDataConfig5Training(double **data,Dimension *data_training, Dimension *dim_data_full);
double **getDataConfig1Test(double **data,Dimension *dim_data);
double **getDataConfig2Test(double **data,Dimension *dim_data);
double **getDataConfig3Test(double **data,Dimension *dim_data);
double **getDataConfig4Test(double **data,Dimension *dim_data);
double **getDataConfig5Test(double **data,Dimension *dim_data);
int converteBooleano(int *cromo, int inicio, int fim);
int **bin2dec_pop(Individual *bin_population, int num_individuals);
void printBidimensionalMatriz(int **matriz, int x, int y);
int get_max(double a,double b,double c);
double getOA(double **data,Dimension dim);
double **get_predict(double **data1,double **data2,double **data3,Dimension dim_data_sets,int *phenotype);
double get_predict_OA(double **data1,double **data2,double **data3,Dimension dim_data_sets,int *phenotype);
int *get_phenotype(Individual *ind,int k);
double fitness_ensemble_classifiers(Individual *ind,int k,double **datatrain1,double **datatrain2,double **datatrain3,Dimension dim_train_sets);


void split(const std::string & str, const string & delimiters, std::vector<std::string> * elems) {

	// Skip delimiters at beginning.
	string::size_type lastPos = str.find_first_not_of(delimiters, 0);
	// Find first "non-delimiter".
	string::size_type pos = str.find_first_of(delimiters, lastPos);

	while (string::npos != pos || string::npos != lastPos) {
		// Found a token, add it to the vector.
		elems->push_back(str.substr(lastPos, pos - lastPos));
		// Skip delimiters.  Note the "not_of"
		lastPos = str.find_first_not_of(delimiters, pos);
		// Find next "non-delimiter"
		pos = str.find_first_of(delimiters, lastPos);
	}

}

void printData(double **data, int x, int y) {
	printf("\n");
	for (int i = 0; i < x; i++) {
		for (int j = 0; j < y; j++)
			cout << " " <<  data[i][j];
		printf("\n");
	}
}

int count_lines(const char* fileName) {
	int linhas = 0;
	ifstream infile;
	infile.open(fileName); // open file
	string line;
	while (getline(infile, line)) {
		linhas++;
	}

	return linhas;
}

//------------------------------------------------------------------------------
double **load_full_file(const char* fileName, Dimension *dim_data){
	int i_linha = count_lines(fileName);
	//double data[i_linha][4]; // trocar por ponteiro
	double **data;
	data = new double*[i_linha];
	for(int i=0;i < i_linha;i++)
			data[i] = new double[4]; // In this problem, there are 4 important values (real and predicted classes)

	dim_data->x = i_linha;
	dim_data->y = 4;

	ifstream infile;
	infile.open(fileName); // open file
	if (infile) {
		string line;
		int j = 0;
		while (!infile.eof()){
			getline(infile, line);
			string temp;
			vector<string> myvector;
			split(line, " ", &myvector);
			for (int i = 0; i < myvector.size(); i++) {
				temp = myvector.at(i);
				if (i == 1) {
					data[j][0] = atof(temp.substr(6, 6).c_str());
				} else if (i > 1) {
					int y = atoi(temp.substr(6, 6).c_str());
					data[j][y] = atof(temp.substr(8, temp.length()).c_str());
				}
			}
			j++;
		}
		infile.close();
	}

	//printData(data,i_linha, 4);
	return data;
}

//------------------------------------------------------------------------------
double **getDataConfig1Training(double **data,Dimension *dim_data_training, Dimension *dim_data_full){
	dim_data_training->x=dim_data_full->x-165;
	dim_data_training->y=dim_data_full->y;
	int x = dim_data_training->x;
	double **dataConfigTraining1;
	dataConfigTraining1 = new double*[x];
	for(int k=0;k<x; k++){
		dataConfigTraining1[k] = new double[4];
	}

	int index = 0;
	for(int i = 165; i<dim_data_full->x; i++, index++){
		for(int j = 0; j<dim_data_training->y; j++){
			dataConfigTraining1[index][j] = data[i][j];
		}
	}
	return dataConfigTraining1;
}

//------------------------------------------------------------------------------
double **getDataConfig2Training(double **data,Dimension *dim_data_training, Dimension *dim_data_full){
	dim_data_training->x = dim_data_full->x - 165;
	dim_data_training->y = dim_data_full->y;
	int x = dim_data_training->x;
	double **dataConfigTraining2;
	dataConfigTraining2 = new double*[x];
	for(int k=0;k<x; k++){
		dataConfigTraining2[k] = new double[4];
	}

	int index = 0;
	for(int i = 0; i<165; i++, index++){
		for(int j = 0; j<dim_data_training->y; j++){
			dataConfigTraining2[index][j] = data[i][j];
		}
	}

	for (int i = 330; i < dim_data_full->x; i++, index++) {
		for (int j = 0; j < dim_data_training->y; j++) {
			dataConfigTraining2[index][j] = data[i][j];
		}
	}

	return dataConfigTraining2;
}
//------------------------------------------------------------------------------
double **getDataConfig3Training(double **data,Dimension *dim_data_training, Dimension *dim_data_full){
	dim_data_training->x = dim_data_full->x - 165;
	dim_data_training->y = dim_data_full->y;
	int x = dim_data_training->x;
	double **dataConfigTraining3;
	dataConfigTraining3 = new double*[x];
	for(int k=0;k<x; k++){
		dataConfigTraining3[k] = new double[4];
	}

	int index = 0;
	for(int i = 0; i<330; i++, index++){
		for(int j = 0; j<dim_data_training->y; j++){
			dataConfigTraining3[index][j] = data[i][j];
		}
	}

	for (int i = 495; i < dim_data_full->x; i++, index++) {
		for (int j = 0; j < dim_data_training->y; j++) {
			dataConfigTraining3[index][j] = data[i][j];
		}
	}

	return dataConfigTraining3;
}

//------------------------------------------------------------------------------
double **getDataConfig4Training(double **data,Dimension *dim_data_training, Dimension *dim_data_full){
	dim_data_training->x = dim_data_full->x - 165;
	dim_data_training->y = dim_data_full->y;
	int x = dim_data_training->x;
	double **dataConfigTraining4;
	dataConfigTraining4 = new double*[x];
	for(int k=0;k<x; k++){
		dataConfigTraining4[k] = new double[4];
	}

	int index = 0;
	for(int i = 0; i<495; i++, index++){
		for(int j = 0; j<dim_data_training->y; j++){
			dataConfigTraining4[index][j] = data[i][j];
		}
	}

	for (int i = 660; i < dim_data_full->x; i++, index++) {
		for (int j = 0; j < dim_data_training->y; j++) {
			dataConfigTraining4[index][j] = data[i][j];
		}
	}

	return dataConfigTraining4;
}
//------------------------------------------------------------------------------
double **getDataConfig5Training(double **data,Dimension *dim_data_training, Dimension *dim_data_full){
	dim_data_training->x = dim_data_full->x - 165;
	dim_data_training->y = dim_data_full->y;
	int x = dim_data_training->x;
	double **dataConfigTraining5;
	dataConfigTraining5 = new double*[x];
	for(int k=0;k<x; k++){
		dataConfigTraining5[k] = new double[4];
	}

	int index = 0;
	for(int i = 0; i<660; i++, index++){
		for(int j = 0; j<dim_data_training->y; j++){
			dataConfigTraining5[index][j] = data[i][j];
		}
	}
	return dataConfigTraining5;
}
//------------------------------------------------------------------------------
double **getDataConfig1Test(double **data,Dimension *dim_data){
	dim_data->x = 165;
	dim_data->y = 4;
	int x = dim_data->x;

	double **dataConfigTest1;
	dataConfigTest1 = new double*[x];
	for(int k=0;k<x; k++){
		dataConfigTest1[k] = new double[4];
	}

	int index = 0;
	for(int i = 0; i<165; i++, index++){
		for(int j = 0; j<4; j++){
			dataConfigTest1[index][j] = data[i][j];
		}
	}

	return dataConfigTest1;

}
//------------------------------------------------------------------------------
double **getDataConfig2Test(double **data,Dimension *dim_data){
	dim_data->x = 165;
	dim_data->y = 4;
	int x = dim_data->x;
	double **dataConfigTest2;
	dataConfigTest2 = new double*[x];
	for(int k=0;k<x; k++){
		dataConfigTest2[k] = new double[4];
	}

	int index = 0;
	for(int i = 165; i<330; i++, index++){
		for(int j = 0; j<dim_data->y; j++){
			dataConfigTest2[index][j] = data[i][j];
		}
	}
	return dataConfigTest2;

}
//------------------------------------------------------------------------------
double **getDataConfig3Test(double **data,Dimension *dim_data){
	dim_data->x = 165;
	dim_data->y = 4;
	int x = dim_data->x;
	double **dataConfigTest3;
	dataConfigTest3 = new double*[x];
	for(int k=0;k<x; k++){
		dataConfigTest3[k] = new double[4];
	}

	int index = 0;
	for(int i = 330; i<495; i++, index++){
		for(int j = 0; j<dim_data->y; j++){
			dataConfigTest3[index][j] = data[i][j];
		}
	}
	return dataConfigTest3;

}
//------------------------------------------------------------------------------
double **getDataConfig4Test(double **data,Dimension *dim_data){
	dim_data->x = 165;
	dim_data->y = 4;
	int x = dim_data->x;
	double **dataConfigTest4;
	dataConfigTest4 = new double*[x];
	for(int k=0;k<x; k++){
		dataConfigTest4[k] = new double[4];
	}

	int index = 0;
	for(int i = 495; i<660; i++, index++){
		for(int j = 0; j<dim_data->y; j++){
			dataConfigTest4[index][j] = data[i][j];
		}
	}
	return dataConfigTest4;

}
//------------------------------------------------------------------------------
double **getDataConfig5Test(double **data, Dimension *dim_data){
	dim_data->x = 165;
	dim_data->y = 4;
	int x = dim_data->x;
	double **dataConfigTest5;
	dataConfigTest5 = new double*[x];
	for(int k=0;k<x; k++){
		dataConfigTest5[k] = new double[4];
	}

	int index = 0;
	for(int i = 660; i<825; i++, index++){
		for(int j = 0; j<dim_data->y; j++){
			dataConfigTest5[index][j] = data[i][j];
		}
	}
	return dataConfigTest5;

}
//------------------------------------------------------------------------------
int converteBooleano(int *cromo, int inicio, int fim){
	int i, inc = 1;
	int aux=0;
	for(i=fim; i>=inicio; i--){
		if(cromo[i] == 1)
			aux += inc;

		inc *= 2;
	}
	return aux;
}
//------------------------------------------------------------------------------
int **bin2dec_pop(Individual *bin_population, int num_individuals){
	int **dec_population;
	dec_population = new int*[num_individuals];
	for(int i=0;i < num_individuals;i++){
		dec_population[i] = new int[3]; //3 classificadores
	}

	int weight_size = 0;
	for(int i = 0; i < num_individuals; i++){
		weight_size = bin_population[i].getNumGenes()/3; //3 classificadores
		for(int j=0; j < 3; j++){
			dec_population[i][j] = converteBooleano(bin_population[i].getChromosome(),weight_size*j, weight_size*(j+1)-1);
		}
	}

	return dec_population;
}
//------------------------------------------------------------------------------
int *get_phenotype(Individual *ind,int k){
	int *phenotype;
	phenotype = new int[3];

	int weight_size = ind[k].getNumGenes()/3;
	for(int i=0;i<3;i++)
		phenotype[i] = converteBooleano(ind[k].getChromosome(),weight_size*i,weight_size*(i+1)-1);

	return phenotype;
}
//------------------------------------------------------------------------------
void printBidimensionalMatriz(int **matriz, int x, int y){
	printf("\n");
	for(int i=0;i<x;i++){
		for(int j=0;j<y;j++)
			printf("%d ",matriz[i][j]);
		printf("\n");
	}
}
//------------------------------------------------------------------------------
int get_max(double a,double b,double c){
    double v[3] = {a,b,c};
    int index[3] = {0,1,2};
    //insertion sort algorithm

    int i,j,tmp;

    for(i = 1;i < 3;i++){
            j = i;
            while(j > 0 && v[index[j-1]] > v[index[j]]){
                tmp = index[j];
                index[j] = index[j-1];
                index[j-1] = tmp;
                j--;
            }
        }

    int classe = index[2]; //retorna o maior

    return classe+1; //pois nao ha classe zero  .... por isso o "mais um"

}
//------------------------------------------------------------------------------
double getOA(double **data,Dimension dim){
    double OA = 0.0;
    int numero_acertos = 0,numero_de_amostras = dim.x,temp;

    for(int i=0;i<dim.x;i++){
        temp = get_max(data[i][1],data[i][2],data[i][3]);
        //printf("\n amostra: %d ---> classe: %d",i+1,temp); pera();
        if(data[i][0] == temp)
            numero_acertos++;
    }

    //printf("\nnumero de acertos: %d \tnumero de amostras: %d\n",numero_acertos,numero_de_amostras);// pera();
    OA = (numero_acertos/(double)numero_de_amostras);

    return OA;
}
//------------------------------------------------------------------------------
double get_predict_OA(double **data1,double **data2,double **data3,Dimension dim_data_sets,int *phenotype){
	double OA = 0.0;
	double **predict_set;
	predict_set = new double*[dim_data_sets.x];
	for (int i = 0; i < dim_data_sets.x; i++)
		predict_set[i] = new double[dim_data_sets.y];

	for(int i=0;i<dim_data_sets.x;i++){
			predict_set[i][0] = data1[i][0];
			predict_set[i][1] = phenotype[0]*data1[i][1] + phenotype[1]*data2[i][1] + phenotype[2]*data3[i][1];
			predict_set[i][2] = phenotype[0]*data1[i][2] + phenotype[1]*data2[i][2] + phenotype[2]*data3[i][2];
			predict_set[i][3] = phenotype[0]*data1[i][3] + phenotype[1]*data2[i][3] + phenotype[2]*data3[i][3];
	}

	OA = getOA(predict_set,dim_data_sets);

	for(int i=0;i<dim_data_sets.x;i++)
		delete[] predict_set[i];
	delete[] predict_set;

	return OA;
}
//------------------------------------------------------------------------------
double **get_predict(double **data1,double **data2,double **data3,Dimension dim_data_sets,int *phenotype){
	double **predict_set;
	predict_set = new double*[dim_data_sets.x];
	for(int i=0;i<dim_data_sets.x;i++)
		predict_set[i] = new double[dim_data_sets.y];

	for(int i=0;i<dim_data_sets.x;i++){
			predict_set[i][0] = data1[i][0];
			predict_set[i][1] = phenotype[0]*data1[i][1] + phenotype[1]*data2[i][1] + phenotype[2]*data3[i][1];
			predict_set[i][2] = phenotype[0]*data1[i][2] + phenotype[1]*data2[i][2] + phenotype[2]*data3[i][2];
			predict_set[i][3] = phenotype[0]*data1[i][3] + phenotype[1]*data2[i][3] + phenotype[2]*data3[i][3];
	}

	return predict_set;
}
//------------------------------------------------------------------------------
double fitness_ensemble_classifiers(Individual *ind,int k,double **datatrain1,double **datatrain2,double **datatrain3,Dimension dim_train_sets){

	double fit = 0.0;
	int *phenotype;
	double **predict_data;


		phenotype = get_phenotype(ind,k);
		predict_data = get_predict(datatrain1,datatrain2,datatrain3,dim_train_sets,phenotype);
		fit = getOA(predict_data,dim_train_sets);


	//liberando memoria
	delete[] phenotype;
	for(int i=0;i<dim_train_sets.x;i++)
		delete[] predict_data[i];
	delete[] predict_data;

	return fit;
}
//------------------------------------------------------------------------------
